////////////////////////////////////////////////
// DESCRIPTION:
//    Sample I2C Device Emulator Component
//
// Legal Notices:
//   Copyright (c) 2008, Telliam Consulting, LLC.
//   All rights reserved.
//
//   Redistribution and use in source and binary forms, with or without modification,
//   are permitted provided that the following conditions are met:
//
//   * Redistributions of source code must retain the above copyright notice, this list
//     of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright notice, this
//     list of conditions and the following disclaimer in the documentation and/or other
//     materials provided with the distribution.
//   * Neither the name of Telliam Consulting nor the names of its contributors may be
//     used to endorse or promote products derived from this software without specific
//     prior written permission.
//
//   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
//   EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
//   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
//   SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
//   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
//   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
//   BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
//   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
//   ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
//   DAMAGE. 
//
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Collections.Generic;

using Microsoft.SPOT.Emulator;
using Microsoft.SPOT.Emulator.I2c;

namespace FusionWare.SPOT.EmulatorComponents
{
    /// <summary>This is a demonstration of an emulated I2C Device</summary>
    /// <remarks>
    /// The device emulated here has a simple protocol. This device suports
    /// one command to read the "Chip ID". Sending the ReadID command (0x5A)
    /// will return the 2 byte ChipID (0xAA55) in little-endian order.
    /// 
    /// Each device will be unique in how it handles the relationship between
    /// the read/write and transaction begin and end. It is possible to throw
    /// an exception to NAK the trnasaction if it was terminated early. You can
    /// also return 0 data or repeat the last data if the Master requests 
    /// more data than available in order to emulate exact behaviour of a chip. 
    /// </remarks>
    public class SimpleI2CDevice : I2cDevice
    {
        /// <summary>Creates a new emulated SimpleI2CDevice</summary>
        public SimpleI2CDevice()
        {
            this._ChipID = 0xAA55;
            this.AddressPointer = Registers.ChipID;
        }

        /// <summary>ChipID read from the configuration file</summary>
        /// <remarks>This is an optional value. If not present the default
        /// 0xAA55, set in the constructor, is used.</remarks>
        public ushort ChipID
        {
            get { return this._ChipID; }
            set
            {
                base.ThrowIfNotConfigurable();
                this._ChipID = value;
            }
        }
        private ushort _ChipID;

        /// <summary>Starts a new transaction</summary>
        /// <remarks>Initializes internal state for a new transaction</remarks>
        protected override void DeviceBeginTransaction()
        {
            base.DeviceBeginTransaction();
            if (this.InTransaction)
                throw new InvalidOperationException("Cannot begin a second transaction");
            
            this.InTransaction = true;
        }

        /// <summary>Terminates a transaction</summary>
        protected override void DeviceEndTransaction()
        {
            base.DeviceEndTransaction();
            if (!this.InTransaction)
                throw new InvalidOperationException("Cannot end a transaction that wasn't started");

            this.InTransaction = false;
        }

        /// <summary>Reads Data from the device</summary>
        /// <param name="data">byte array to receive the data read</param>
        protected override void DeviceRead(byte[] data)
        {
            switch(this.AddressPointer)
            {
            case Registers.ChipID:
                ReadChipID(data);
                break;

            case Registers.Scratch8:
                ReadScratch8(data);
                break;

            case Registers.Scratch16:
                ReadScratch16(data);
                break;
            
            default:
                throw new InvalidOperationException();
            }
        }

        /// <summary>Writes data to the emulated device</summary>
        /// <param name="data">data to write</param>
        /// <remarks>
        /// This method only responds to a single read ID command
        /// </remarks>
        protected override void DeviceWrite(byte[] data)
        {
            if(data.Length < 1 || data.Length > 3)
                throw new ArgumentException("Invalid Length","data");

            this.AddressPointer = (Registers)data[0];

            // if it's just a write to the "address Pointer"
            // then don't do anything else
            if(data.Length == 1)
                return;

            switch(this.AddressPointer)
            {
            case Registers.Scratch8:
                WriteScratch8(data);
                break;

            case Registers.Scratch16:
                WriteScratch16(data);
                break;

            case Registers.ChipID:
            default:
                throw new InvalidOperationException();
            }
        }
        
        #region Register Read Methods
        private void ReadScratch16(byte[] data)
        {
            if(data.Length < 1 || data.Length > 2)
                throw new ArgumentException( "Invalid Length", "data" );

            data[0] = (byte)this.Scratch16;
            if(data.Length > 1)
                data[1] = (byte)(this.Scratch16 >> 8);
        }

        private void ReadScratch8(byte[] data)
        {
            if(data.Length != 1 )
                throw new ArgumentException( "Invalid Length", "data" );

            data[0] = this.Scratch8;
        }

        private void ReadChipID(byte[] data)
        {
            if(data.Length < 1 || data.Length > 2)
                throw new ArgumentException( "Invalid Length", "data" );
            
            data[0] = (byte)this._ChipID;
            if(data.Length > 1)
                data[1] = (byte)(this._ChipID >> 8);
        }
        #endregion

        #region Register Write Methods
        private void WriteScratch16(byte[] data)
        {
            if(data.Length < 2 || data.Length > 3)
                throw new ArgumentException( "Invalid Length", "data" );

            this.Scratch16 = data[1];
            if(data.Length > 2)
                this.Scratch16 |= (ushort)(data[2] << 8);
        }

        private void WriteScratch8(byte[] data)
        {
            if(data.Length != 2)
                throw new ArgumentException( "Invalid Length", "data" );

            this.Scratch8 = data[1];
        }

        #endregion

        enum Registers : byte
        {
            ChipID = 0x5A,
            Scratch8,
            Scratch16,
        }

        bool InTransaction;
        Registers AddressPointer;
        byte Scratch8;
        ushort Scratch16;
    }
}
